#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef void (*FUNC)(char *);

//定义一个结构体，结构体的数据类型为struct student。
//TIPS:结构体内的变量是成员变量而非全局变量，不会默认初始化
struct student {
    char name[32];
    char sex;
    int age;
    //结构体里面的成员变量不能初始化
//    int num = 10;
    char tel[12];
    char addr[100];

    //将函数指针应用在结构体中
    void (*f1)(char *);

    FUNC f2;
};

//不常用的结构体变量定义:在结构体定义后面直接加上变量名。此处的A、B、C都是结构体变量。
struct Stu {
    int age;
    char sex;
} A, B, C;

void play_basketball(char *name) {
    printf("%s去打篮球了\n", name);
}

void play_football(char *name) {
    printf("%s去打足球了\n", name);
}

struct publish {
    char name[32];
    char addr[32];
};

//结构体中的成员变量为其他结构体变量/指针
struct book {
    char name[32];           //名字
    unsigned int price;      //价格
    struct student holder;   //持有者
    struct publish *publish; //出版社
};

/**
 * 结构体。
 * 在自然界的任何一个事物，都有多种属性，如果用计算机语言来描述的话，一种属性也许可以使用某种基本数据类型来表示，但是若存在多个属性的时候，
 * 一个基本数据类型就不够表示了。例如 学生: 姓名、性别、年龄、电话号码、家庭地址。而能解决这点的就是"结构体"。
 *
 * 语法:
 * stuct 结构体名称
 * {
 *  成员变量1;
 *  ...
 *  成员变量n;
 * };
 * TIPS:结构体里面的成员变量不能初始化
 * TIPS:在结构体内不能定义函数(因为不能初始化)，但是可以定义函数指针。
 * TIPS:结构体和联合体这种被称为"复合数据类型"
 *
 */
int main() {
    //定义一个结构体变量。
    struct student student;
    struct student *p;
    struct student sAddr[10];
    //普通的赋值
    A.age = 16;
    //直接初始化(在定义变量的时候直接赋予初始值)——注意初始化的值和定义结构体时成员变量的定义顺序一致！
    struct student student1 = {
            "张三",
            'm',
            18};
    //可以通过.成员变量的方式无视掉定义顺序并赋值——但是注意定义顺序不会因此而改变！
    struct student student2 = {
            .name = "张三",
            //提前设了值
            .age = 18,
            .tel = "阿萨德",
            .addr = "中国",
            // sex才是顺位第二，所以设置完后当前顺序是轮到顺位第三，也就是age
            .sex = 'g',
            //设置顺位第三的age的值，轮到顺位第四，也就是tel
            19,
            "10086"};
    //数组的初始化
    struct student s2Addr[2] = {{.name = "张三"},
                                {.name = "李四"}};
    printf("结构体student占用的内存空间:%d\n", sizeof(struct student));
    printf("结构体Stu占用的内存空间:%d\n", sizeof(struct Stu));

    //结构体中成员变量的访问
    // 1、. : 域操作。前提:结构体变量是个普通变量/数组。
    printf("结构体变量student2的name:%s\n", student2.name);
    printf("结构体变量A的age:%d\n", A.age);
    printf("结构体数组变量s2Addr的name:%s\n", s2Addr[1].name);
    // 2、-> : 指向操作。前提:结构体变量是个指针变量。
    p = &student2;
    printf("结构体变量student2的age:%d\n", p->age);
    // 3、如果结构体指针变量指向了某块内存空间，则可以通过三种方式来操作指针指向的内存空间
    struct Stu *stu;
    stu = (struct Stu *) malloc(3 * sizeof(struct Stu));
    //(1)下标法(不常用)。point[i].成员变量
    stu[0].age = 10;
    //(2)指针法(不常用)。(*(point+i)).成员变量
    (*(stu + 1)).age = 15;
    //(3)指向操作符。因为(point+i)==&point[i]，所以可以(point+i)->成员变量
    (stu + 1)->age = 12;

    //操作结构体中的函数指针成员变量
    student2.f1 = play_football;
    student2.f1(student2.name);
    student2.f2 = play_basketball;
    student2.f2(student2.name);

    //既然结构体本质上还是一种数据类型，那么回忆一下全局变量以及局部变量的概念，我们是否可认为存在着局部结构体变量呢？答案是存在的。
    struct Stu {
        int money;
        char address[20];
    } Stu;
    Stu.money = 40;
    printf("局部结构体变量Stu的money:%d\n", Stu.money);
    //typedef关键字的作用是定义一个新的数据类型，同理，对结构体数据结构也是能起到一样的作用。
    {
        typedef struct Stu {
            int money;
            char *address;
        } Stu;
        //若使用了typedef，则在结构体后面加上的名称就并非是初始的变量名，而是 struct Stu 这个结构体类型的一个别名。
        Stu stu;
        stu.address = "这是一个地址";
        printf("局部结构体变量stu的address:%s\n", stu.address);
        //其实本质上就是相当于下面的定义，这是上面的写法可以同时将结构体的结构也给定义了所以更常用
        typedef struct Stu Stu2;
        typedef struct Stu *P_Stu;
    }


    //访问结构体中的结构体成员变量
    struct book book = {
            .name = "书本名称",
            .price = 56
    };
    book.holder = student1;
    printf("结构体变量book中的结构体成员变量holder的age:%d\n", book.holder.age);
    book.publish = (struct publish *) malloc(2 * sizeof(struct publish)); //假设该书籍对应有两个出版社
    strcpy(book.publish[0].name, "新华出版社");
    strcpy(book.publish[1].name, "广州出版社");
    printf("结构体变量book中的结构体成员变量publish的name:%s\n", (book.publish)->name);
    printf("结构体变量book中的结构体成员变量publish的name:%s\n", (book.publish + 1)->name);


    printf("--------------------------------------------------------------\n");
    //将一个结构体中的内容赋值给另外一个结构体
    struct book book2 = book;
    printf("存储原结构体变量本身的地址:%p\n", &book);
    printf("原结构体变量指向的地址:%p\n", book);
    printf("原结构体变量的所有成员变量指向的地址:%p %p %p %p\n", book.name, book.price, book.holder, book.publish);
    printf("原结构体变量中存储每个成员变量本身的地址:%p %p %p %p\n", &book.name, &book.price, &book.holder, &book.publish);
    printf("存储另一个结构体变量本身的地址:%p\n", &book2);
    printf("另一个结构体变量指向的地址:%p\n", book2);
    //可以看到，除了数组变量之外，复制过来的属性依旧指向着同一块内存空间——也就是说两个指针会互相影响。
    printf("另一个结构体变量的所有成员变量指向的地址:%p %p %p %p\n", book2.name, book2.price, book2.holder, book2.publish);
    printf("另一个结构体变量中存储每个成员变量本身的地址:%p %p %p %p\n", &book2.name, &book2.price, &book2.holder, &book2.publish);

    printf("原结构体的所有成员变量值:%s %d %d %s\n", book.name, book.price, book.holder.age, book.publish->name);
    //另一个结构体中进行了修改
    strcpy(book2.name, "另一本书的书本");
    strcpy(book2.publish[0].name, "北京出版社");
    // 如果是非指针数据类型的话，那问题不大，因为非指针数据类型存储的不是引用，所以即便修改了也不会影响另外一个结构体。
    // 但是若是指针类型的话，因为必然是通过引用进行修改的，所以就会有影响。遇到这种情况，解决办法就是为该指针变量重新分配内存了。
    printf("原结构体的所有成员变量值(修改后):%s %d %d %s\n", book.name, book.price, book.holder.age, book.publish->name);
    //为book2.publish单独分配空间
    book2.publish = (struct publish *) malloc(sizeof(struct publish));
    strcpy(book2.publish->name, "又改成了另外一个出版社的名称");
    printf("原结构体的所有成员变量值(这次就不会影响到了):%s %d %d %s\n", book.name, book.price, book.holder.age, book.publish->name);
    //那如果我还想要将原结构体指针内容复制给另一个结构体怎么办？可以用memcpy——比strcpy更通用。
    // 将book.publish这个指针所指向的空间中的内容拷贝到book2.publish这个指针所指向的空间中。
    // TIPS:如果publish结构体里面也有指针变量则也需要做一样的事情
    memcpy(book2.publish, book.publish, sizeof(struct publish));
    printf("另一个结构体的指针变量publish的值(复制自原结构体):%s\n", book2.publish->name);

    free(book.publish);
    free(book2.publish);
    return 0;
}
